// Parser.java

public class Parser {

    private Scanner scanner;

    /**
     * See if the given string matches the token in the scanner.
     * @param s
     * @throws SyntaxException
     */
    private void match(String s) throws SyntaxException {
    	scanner.match(new Token(s));
    }

    /**
     * Return the current token in the scanner.
     * @return
     * @throws SyntaxException
     */
    private Token curr() throws SyntaxException {
    	return scanner.curr();
    }

    /**
     * Return the current position in the scanner.
     * @return
     */
    private int pos() {
    	return scanner.pos();
    }

    /**
     * Try to parse multiple/division operator.
     * @return
     * @throws SyntaxException
     */
    private NodeMulop parseMulop() throws SyntaxException {
		if (curr().equals(new Token("*"))) {
		    match("*");
		    return new NodeMulop(pos(),"*");
		}
		if (curr().equals(new Token("/"))) {
		    match("/");
		    return new NodeMulop(pos(),"/");
		}
		return null;
    }

    /**
     * Try to parse a add/minus operator.
     * @return
     * @throws SyntaxException
     */
    private NodeAddop parseAddop() throws SyntaxException {
		if (curr().equals(new Token("+"))) {
		    match("+");
		    return new NodeAddop(pos(),"+");
		}
		if (curr().equals(new Token("-"))) {
		    match("-");
		    return new NodeAddop(pos(),"-");
		}
		return null;
    }
    
    /**
     * Try to parse a realtional operator...10/27/14
     * @return
     * @throws SyntaxException
     */
    private NodeRelop parseRelop() throws SyntaxException {
    	if (curr().equals(new Token("<"))) {
    		match("<");
    		return new NodeRelop(pos(),"<");
    	}
    	if (curr().equals(new Token("<="))) {
    		match("<=");
    		return new NodeRelop(pos(),"<=");
    	}
    	if (curr().equals(new Token(">"))) {
    		match(">");
    		return new NodeRelop(pos(),">");
    	}
    	if (curr().equals(new Token(">="))) {
    		match(">=");
    		return new NodeRelop(pos(),">=");
    	}
    	if (curr().equals(new Token("<>"))) {
    		match("<>");
    		return new NodeRelop(pos(),"<>");
    	}
    	if (curr().equals(new Token("=="))) {
    		match("==");
    		return new NodeRelop(pos(),"==");
    	}
    	return null;
    }

    /**
     * Parse a fact.
     * @return
     * @throws SyntaxException
     */
    private NodeFact parseFact() throws SyntaxException {
		if (curr().equals(new Token("("))) {
		    match("(");
		    NodeExpr expr=parseExpr();
		    match(")");
		    return new NodeFactExpr(expr);
		}
		if (curr().equals(new Token("id"))) {
		    Token id=curr();
		    match("id");
		    return new NodeFactId(pos(),id.lex());
		}
		// else, start with '-' unary minus operator.
		if (curr().equals(new Token("-"))) {
			match("-");
			if (curr().equals(new Token("("))) {
				match("(");
				NodeExpr expr = parseExpr();
				match(")");
				return new NodeFactExpr(expr, true);
			}
			if (curr().equals(new Token("id"))) {
				Token id=curr();
				match("id");
				return new NodeFactId(pos(),id.lex(),true);
			}
			Token num=curr();
			match("num");
			return new NodeFactNum(num.lex(), true);
		}
		Token num=curr();
		match("num");
		return new NodeFactNum(num.lex());
    }

    /**
     * Parse a term.
     * @return
     * @throws SyntaxException
     */
    private NodeTerm parseTerm() throws SyntaxException {
		NodeFact fact=parseFact();
		NodeMulop mulop=parseMulop();
		if (mulop==null)
		    return new NodeTerm(fact,null,null);
		NodeTerm term=parseTerm();
		term.append(new NodeTerm(fact,mulop,null));
		return term;
    }

    /**
     * Parse a expression.
     * @return
     * @throws SyntaxException
     */
    private NodeExpr parseExpr() throws SyntaxException {
		NodeTerm term=parseTerm();
		NodeAddop addop=parseAddop();
		if (addop==null)
		    return new NodeExpr(term,null,null);
		NodeExpr expr=parseExpr();
		expr.append(new NodeExpr(term,addop,null));
		return expr;
    }

    /**
     * Parse a boolean exprssion.
     * @return
     * @throws SystaxException
     */
    private NodeBoolexpr parseBoolexpr() throws SyntaxException { 
    	NodeExpr expr1 = parseExpr();
    	scanner.skipCmnt();
    	NodeRelop relop = parseRelop();
    	scanner.skipCmnt();
    	NodeExpr expr2 = parseExpr();
    	return new NodeBoolexpr(relop,expr1,expr2);
    }
    
    /**
     * Parse an assignment.
     * @return
     * @throws SyntaxException
     */
    private NodeAssn parseAssn() throws SyntaxException {
    	Token id=curr();
		match("id");
		scanner.skipCmnt();
		match("=");
    	scanner.skipCmnt();
		NodeExpr expr=parseExpr();
		NodeAssn assn=new NodeAssn(id.lex(),expr);
		return assn;
    }

    /**
     *
     * Parse a statement.
     * @param input 
     * @return
     * @throws SyntaxException
     */
    private NodeStmt parseStmt(java.util.Scanner input) throws SyntaxException {
    	scanner.skipCmnt();
    	// rd x
		if (curr().equals(new Token("rd"))) {
			//System.out.println(curr());
		    match("rd");
		    Token id = curr();
		    //System.out.println("HERE" + curr());
		    match("id");
		    //System.out.println("Enter value for " + id.lex() + ": ");
		    return new NodeStmtRead(input, id);
		}
		// write y
		if (curr().equals(new Token("wr"))) {
			match("wr");
			scanner.skipCmnt();
			NodeExpr expr = parseExpr();
			scanner.skipCmnt();
			return new NodeStmtWrite(expr);
		}
			
		// 'if-then(-else)'
		if (curr().equals(new Token("if"))) {
			match("if");
			scanner.skipCmnt();
			NodeBoolexpr boolexpr = parseBoolexpr();
			scanner.skipCmnt();
			match("then");
			scanner.skipCmnt();
			NodeStmt stmt1 = parseStmt(input);
			scanner.skipCmnt();
			if (scanner.done())
				return new NodeIfBlock(boolexpr,stmt1,null);
			if (curr().equals(new Token("else"))) {
				match("else");
				NodeStmt stmt2 = parseStmt(input);
				return new NodeIfBlock(boolexpr,stmt1,stmt2);
			} else
				return new NodeIfBlock(boolexpr,stmt1,null);
		}
			
		// 'while-do'
		if (curr().equals(new Token("while"))) {
			match("while");
			scanner.skipCmnt();
			NodeBoolexpr boolexpr = parseBoolexpr();
			scanner.skipCmnt();
			match("do");
			scanner.skipCmnt();
			NodeStmt stmt = parseStmt(input);
			scanner.skipCmnt();
			return new NodeWhileDo(boolexpr, stmt);		
		}
			
		// 'begin-end'
		if (curr().equals(new Token("begin"))) {
			match("begin");
			scanner.skipCmnt();
			NodeBlock block = parseBlock(input);
			scanner.skipCmnt();
			match("end");
			return new NodeBeginEnd(block);
		}

    	// If no keyword found: then it is an assignment.
    	NodeAssn assn=parseAssn();
    	scanner.skipCmnt();
		//match(";"); // don't need for IA-2.
		NodeStmt stmt=new NodeStmtAssn(assn);
		return stmt;
    }
    
    /**
     * Parse a Block node.
     * @param input 
     * @return
     * @throws SyntaxException 
     */
	private NodeBlock parseBlock(java.util.Scanner input) throws SyntaxException {
		scanner.skipCmnt();
		// branch 1: block : stmt
		NodeStmt stmt = parseStmt(input);
		if (scanner.done())
			return new NodeBlock(stmt, null);
		// branch 2: block : stmt ';' block
		scanner.skipCmnt();
		// if curr() is ";", keep parsing block. If not, just return.
		if (!curr().equals(new Token(";")))
			return new NodeBlock(stmt, null);
		match(";");
		scanner.skipCmnt();
		NodeBlock block = parseBlock(input);
		return new NodeBlock(stmt,block);
	}
    
    /**
     * Parse a program Node.
     * @param input 
     * @return
     * @throws SyntaxExcetion
     */
    private NodeProg parseProg(java.util.Scanner input) throws SyntaxException {
    	scanner.skipCmnt();
    	NodeBlock block = parseBlock(input);
    	scanner.skipCmnt();
    	NodeProg prog = new NodeProg(block);
    	return prog;
    }

	/**
     * Parse the program.
     * @param program
	 * @param input 
     * @return
     * @throws SyntaxException
     */
    public Node parse(String program, java.util.Scanner input) throws SyntaxException {
		scanner=new Scanner(program);
		scanner.next();
		return parseProg(input);		// ia-2
    }

}
